package queryx

import (
	"errors"
	"strings"
	"time"

	"github.com/jinzhu/now"
	"github.com/tidwall/gjson"
	"gorm.io/gorm/clause"
)

var (
	supportOpers  = []string{"in", "eq", "nin", "range"}
	supportFields = []string{"age", "sex", "address", "device", "visited", "projects", "tags"}
	// supportFields = []string{"age", "sex", "address", "device", "tags"}
)

// ParseToExpressions parse condition json to clause.Expression for gorm builder
// field: age, sex, address, device, visited, projects, tags
// oper: in, nin, eq

// type operation struct {
// 	Field string      `json:"field"`
// 	Oper  string      `json:"oper"`
// 	Value interface{} `json:"value"`
// }

func ParseToExpressions(cond string) ([]clause.Expression, error) {
	if !gjson.Valid(cond) {
		return nil, errors.New("invalid json")
	}
	res := gjson.Parse(cond)

	if !res.IsArray() {
		return nil, errors.New("invalid json array")
	}
	var exps []clause.Expression
	res.ForEach(func(key, value gjson.Result) bool {
		field := value.Get("field")
		oper := value.Get("oper")
		val := value.Get("value")
		if !field.Exists() || !oper.Exists() || !val.Exists() {
			// err = errors.New("condition miss key")
			// keep iterating;false will stop iterate
			return true
		}

		dbField := field.String()
		if !inArray(oper.String(), supportOpers) || !inArray(dbField, supportFields) {
			return true
		}

		if dbField == "device" {
			dbField = "last_device"
		}

		switch oper.String() {
		case "eq":
			var eqVal interface{}
			if val.IsArray() {
				eqVal = val.Array()[0]
			} else {
				eqVal = val.Value()
			}
			if dbField == "age" {
				dbField = "year(birthday)"
				age := eqVal.(int)
				exps = append(exps, clause.Eq{
					Column: clause.Expr{SQL: "YEAR(?)", Vars: []interface{}{clause.Column{Name: "birthday"}}},
					Value:  now.With(time.Now().AddDate(-age, 0, 0)).Year()})
			} else {
				exps = append(exps, clause.Eq{Column: dbField, Value: eqVal})
			}
		case "in":
			if !val.IsArray() {
				return true
			}
			var vs []interface{}
			for _, v := range val.Array() {
				if dbField == "tags" || dbField == "projects" || dbField == "sex" {
					vs = append(vs, v.Int())
				} else {
					vs = append(vs, v.Value())
				}

			}
			if dbField == "address" {
				tmpAddr := parseAddress(vs)
				var eqs []clause.Expression
				for k, v := range tmpAddr {
					eqs = append(eqs, clause.IN{Column: k, Values: v})
				}
				exps = append(exps, clause.Or(eqs...))
			} else if dbField == "tags" {
				var onExps []clause.Expression
				onExps = append(onExps,
					clause.Eq{Column: "staff_resource.id", Value: "taggables.object_id"},
					clause.Eq{Column: "taggables.object_type", Value: "App\\\\Models\\\\StaffResource"},
					clause.IN{Column: "taggables.tag_id", Values: vs},
				)
				jc := clause.From{
					Tables: []clause.Table{{Name: "staff_resource"}},
					Joins: []clause.Join{
						{
							Type:  clause.LeftJoin,
							Table: clause.Table{Name: "taggables"},
							ON:    clause.Where{Exprs: onExps},
						},
					},
				}
				exps = append(exps, jc)
			} else if dbField == "projects" {

			} else {
				exps = append(exps, clause.IN{Column: dbField, Values: vs})
			}
		case "nin":
			if !val.IsArray() {
				return true
			}
			var vs []interface{}
			for _, v := range val.Array() {
				vs = append(vs, v.Value())
			}
			exps = append(exps, clause.Neq{Column: dbField, Value: vs})
		case "range":
			if !val.IsArray() || len(val.Array()) < 2 {
				return true
			}
			tmp := val.Array()
			if dbField == "age" {
				dbField = "year(birthday)"
				tmpAge1 := tmp[0].Int()
				tmpAge2 := tmp[1].Int()
				lesser := now.With(time.Now().AddDate(-int(tmpAge2), 0, 0)).Year()
				bigger := now.With(time.Now().AddDate(-int(tmpAge1), 0, 0)).Year()
				// exps = append(exps, clause.Gte{Column: dbField, Value: lesser}, clause.Lte{Column: dbField, Value: bigger})
				exps = append(exps, clause.Gte{
					Column: clause.Expr{
						SQL:  "YEAR(?)",
						Vars: []interface{}{clause.Column{Name: "birthday"}}}, Value: lesser}, clause.Lte{Column: clause.Expr{SQL: "YEAR(?)", Vars: []interface{}{clause.Column{Name: "birthday"}}}, Value: bigger})
			} else {
				exps = append(exps, clause.Gte{Column: dbField, Value: tmp[0].Value()}, clause.Lte{Column: dbField, Value: tmp[1].Value()})
			}

		}
		return true
	})

	return exps, nil
}

// parseAddress
// "110000,110100,110102" => "district":"110102"
func parseAddress(addr []interface{}) map[string][]interface{} {
	addrMap := make(map[string][]interface{})
	var districts []interface{}
	var citys []interface{}
	var provinces []interface{}
	for _, v := range addr {
		addrArr := strings.Split(v.(string), ",")
		switch len(addrArr) {
		case 3:
			districts = append(districts, addrArr[2])
		case 2:
			citys = append(citys, addrArr[1])
			// addrMap["city"] = addrArr[1]
		case 1:
			// addrMap["province"] = addrArr[0]
			provinces = append(provinces, addrArr[0])
		default:
			// 异常，暂不处理
		}
	}
	if len(districts) > 0 {
		addrMap["district"] = districts
	}
	if len(citys) > 0 {
		addrMap["city"] = citys
	}
	if len(provinces) > 0 {
		addrMap["province"] = provinces
	}
	return addrMap
}

func inArray(s string, stack []string) bool {
	for _, v := range stack {
		if s == v {
			return true
		}
	}
	return false
}
